/*
 * GeldkarteCardService.java
 * This class contains methods for the communication with 'Geldkarten'.
 * GeldkarteCardService extends JSmexCardService and implements all its abstract methods. 
 *
 * Created on 5. Oktober 2006, 10:31
 *
 *  This file is part of JSmex.
 *
 *  JSmex is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JSmex is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jsmex.cardservice;

import java.util.Vector;
import jsmex.function.FileProperties;
import jsmex.function.SmartCardFile;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.terminal.CardTerminalException;
import opencard.core.util.HexString;
import jsmex.JSmexTools;

/**
 *
 * @author Tobias Senger
 */
public class GeldkarteCardService extends JSmexCardService {
    
    private static CommandAPDU command = new CommandAPDU(22);
    private static ResponseAPDU response;
    private static byte[] selectedFID = new byte[2];
    public final byte [] DF_BOERSE_AID = { (byte)0xD2,  (byte)0x76, (byte)0x00, (byte)0x00, (byte)0x25, (byte)0x45, (byte)0x50, (byte)0x02, (byte)0x00};
    
    /** Creates a new instance of GeldkarteCardService */
    public GeldkarteCardService() {
        super();
    }
 
    
    
    /**This method checks if the EFs and DFs which are get in parameter filearray.
     * If the file exist, an new instance of <code>SmartCardFile</code> will builded. 
     * All founded SmartCardFiles will be returned in an array.
     * 
     * @param filearray A two-dimensional array which contains FIDs and names of EFs and DFs. 
     * @return An array of SmartCardFile.
     */
    private SmartCardFile[] buildFileList(String[][] filearray) {
        
        ResponseAPDU resp = null;
        SmartCardFile[] scfiles = null;
        Vector filevec = new Vector();
        
        lt.info("called method buildFileList(String[][] filearray)", this);
        
        for (int i=0; i<filearray.length;i++){
            try {
                if (filearray[i][0].charAt(0)=='E')                             // DF or EF?
                    resp = selectFID((byte)0x02,(byte)0x04,filearray[i][1]);    //erstmal testen ob es das EF auch wirklich gibt...
                else
                    resp = selectFID((byte)0x01,(byte)0x04,filearray[i][1]);    //erstmal testen ob es das DF auch wirklich gibt...
                if (resp.sw1()==(byte)0x90) { // Wenn ja die eigenschaften herausfinden
                    FileProperties properties = getProperties(resp);
                    filevec.add(new SmartCardFile(filearray[i][0], properties)); // und wird ein neues File erzeugt und in einem Vektor abgelegt.
                }
            } 
            catch (Exception exc) {
                lt.warning("method buildFileList(String[][] filearray) throws exception. "+exc.toString(), this);
            }
        }
        
        scfiles = new SmartCardFile[filevec.size()];  // Länge für das Array aus Vectorgröße ermittel
        scfiles =  (SmartCardFile[]) filevec.toArray(scfiles); // Inhalt des Vectors in ein Array umwandeln
        return scfiles;
    }
    
    
    
    /**This method returns an array of SmartCardFile which contains all founded
     * files under the DF which ist specified in parameter scfile.
     * 
     * @param scfile Contains a SmartCardFile instance of a DF.
     * @return An array of SmartCardFile.
     */
    public SmartCardFile[] getFileList(SmartCardFile scfile) {
        
        lt.info("called method getFileList(SmartCardFile scfile). scfile name is"+scfile.toString()+". scfile FID is"+HexString.hexify(scfile.fp.getFID()), this);
        
        if (scfile.fp.getFID()[0]==(byte)0x3F && scfile.fp.getFID()[1]==(byte)0x00) {                 //---------MF--------------            
            String[][] mffiles = {{"EF DIR","2F 00"},{"EF KEY","00 10"},{"EF KEYD","00 13"},{"EF AUT","00 11"},{"EF AUTD","00 14"},{"EF PWD0","00 12"},{"EF PWD1","00 22"},{"EF PWDD0","00 15"},{"EF PWDD1","00 25"}
            ,{"EF FBZ","00 16"},{"EF ID","00 03"},{"EF INFO","01 00"},{"EF LOG","00 04"},{"EF RAND","00 05"},{"EF VERSION","00 17"},{"EF RULE","00 30"}
            ,{"DF BÖRSE","A2 00"}};
            
            selectMF();
            
            return buildFileList(mffiles);
        }
        
        else if (scfile.fp.getFID()[0]==(byte)0xA2 && scfile.fp.getFID()[1]==(byte)0x00) {            //---------DF BÖRSE--------------
            String[][] dfboersefiles = {{"EF KEY","00 10"},{"EF KEYD","00 13"},{"EF AUT","00 11"},{"EF AUTD","00 14"},{"EF PWD0","00 12"},{"EF PWDD0","00 15"},{"EF RULE","00 30"}
            ,{"EF BETRAG","01 04"},{"EF BÖRSE","01 05"},{"EF LSEQ","01 06"},{"EF BSEQ","01 07"},{"EF LLOG","01 08"},{"EF BLOG","01 09"}};
            
            selectFID((byte)0x01,(byte)0x0C,"A2 00");
            
            return buildFileList(dfboersefiles);
        }
        
        else return null;
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It selects the MF of the Geldkarte.
     * 
     * @return Returns 'true' if everything went well, 
     * otherwise 'false' will be returned.
     */
    public boolean selectMF() {
        
        ResponseAPDU resp = null;
        byte[] select_cmd= {(byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x0C};
        
        command.setLength(0);
        command.append(select_cmd);;
        
        try {
            resp = sendCommandAPDU(command);
            
            selectedFID[0] = (byte)0x3F;
            selectedFID[1] = (byte)0x00;
            
        } catch (CardTerminalException cte) {
            System.out.println("CardTerminalException: ");
            System.out.println(cte.getMessage() );
        }
        if (resp.sw()==0x9000) {
            lt.info("called method selectMF(). return is \"true\"", this);
            return true;
        }
        else {
            lt.info("called method selectMF(). return is \"false\"", this);
            return false;
        }
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It select the a EF whose FID comes in parameter fid.
     * 
     * @param fid The FID to be selected in an byte-array.
     * @return Returns the ReponseAPDU of the select commando.
     */
    public ResponseAPDU selectEF(byte[] fid) {
        lt.info("called method selectEF("+HexString.hexify(fid)+")",this);
        return selectFID((byte)2,(byte)0x04,fid);
    }
    
    /** This method implements the abstract method from JSmexCardService.
     *  It select the a DF whose FID comes in parameter fid.
     * 
     * @param fid The FID to be selected in an byte-array.
     * @return Returns the ReponseAPDU of the select commando.
     */
    public ResponseAPDU selectDF(byte[] fid) {
        lt.info("called method selectDF("+HexString.hexify(fid)+")",this);
        return selectFID((byte)1,(byte)0x04,fid);
    }
    
    /**This method selects a EF, DF or AID
     *
     * @param p1 If P1 = <ul>
     *                      <li>0: Select MF</li> 
     *                      <li>1: select DF by FileID</li> 
     *                      <li>2: select EF by FileID</li>
     *                      <li>3: select upper DF</li> 
     *                      <li>4: select DF by Name(AID)</li>
     *                   </ul>
     * @param p2 If P2 = <ul>
     *                      <li>0x00: response will be FCI</li>
     *                      <li>0x04: response will be FCP</li>
     *                      <li>0x08: response will be FMD</li>
     *                      <li>0x0C: no response</li>
     *                   </ul>
     * @param fid FID or AID in a String.
     * @return Returndata as selected in P2 in a <code>ResponseAPDU</code>.
     */
    public ResponseAPDU selectFID(byte p1, byte p2, String fid) {
        lt.info("called method selectFID(p1:"+HexString.hexify(p1)+", p2:"+HexString.hexify(p2)+", fid:"+fid+")",this);
        return selectFID(p1, p2, JSmexTools.parseHexString(fid));
    }
    
    /**This method selects a EF, DF or AID
     *
     * @param p1 If P1 = <ul>
     *                      <li>0: Select MF</li> 
     *                      <li>1: select DF by FileID</li> 
     *                      <li>2: select EF by FileID</li>
     *                      <li>3: select upper DF</li> 
     *                      <li>4: select DF by Name(AID)</li>
     *                   </ul>
     * @param p2 If P2 = <ul>
     *                      <li>0x00: response will be FCI</li>
     *                      <li>0x04: response will be FCP</li>
     *                      <li>0x08: response will be FMD</li>
     *                      <li>0x0C: no response</li>
     *                   </ul>
     * @param fid FID or AID in an byte-array.
     * @return Returndata as selected in P2 in a <code>ResponseAPDU</code>.
     */
    public ResponseAPDU selectFID(byte p1, byte p2, byte[] fid) {
        
        byte[] select_cmd= {(byte)0x00, (byte)0xA4, p1, p2, (byte)fid.length};
        
        command.setLength(0);
        command.append(select_cmd);
        command.append(fid);
        
        if (p2==(byte)0x00 || p2==(byte)0x04 || p2==(byte)0x08) command.append((byte)0x00);
        
        try {
            response = sendCommandAPDU(command);
            
            selectedFID[0] = fid[0];
            selectedFID[1] = fid[1];
            
        } catch (CardTerminalException cte) {
            lt.warning("method selectFID throws an CardTerminalException.",this);
        }
        
        lt.info("called method selectFID(p1:"+HexString.hexify(p1)+", p2:"+HexString.hexify(p2)+", fid:"+HexString.hexify(fid)+
                "). response SW: "+HexString.hexify(response.sw1())+" "+HexString.hexify(response.sw2()),this);
        return response;
    }
       
    /**
     * This method implements the abstract method from JSmexCardService.
     * It reads the record with the number recno of the currently selected EF.
     * 
     * @param recno The record which will be read.
     * @param length The length of the record.
     * @return Returns the record data in a ResponseAPDU.
     */
    public ResponseAPDU readRecord(byte recno, int length) {
        
        byte[] rr_cmd = { (byte)0x00, (byte)0xB2, recno, (byte)0x04, (byte)0x00};
        command.setLength(0);
        command.append(rr_cmd);
        
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method readRecord(byte recno, int length) throws an CardTerminalException.",this);
        }
        
        lt.info("called method readRecord(recno:"+HexString.hexify(recno)+", length:"+Integer.toString(length)+")",this);
        return response;
        
    }
       
    /**
     * This method reads the record with the number recno from the EF with the SFI which comes in argument sfi.
     * 
     * @param recno The record which will be read.
     * @param sfi The SFI (short file identifier) of the EF.
     * @return Returns the record data in a ResponseAPDU.
     */
    public ResponseAPDU readRecord(byte recno, byte sfi) {
        
        byte p2 = (byte)((sfi<<3)|0x04); //calculate the P2-parameter fromthe given SFI (Short File Indicator)
        byte[] rr_cmd = { (byte)0x00, (byte)0xB2, recno, p2, (byte)0x00};
        command.setLength(0);
        command.append(rr_cmd);
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            lt.warning("method readRecord(byte recno, byte sfi) throws an CardTerminalException.",this);
        }
        
        lt.info("called method readRecord(recno:"+HexString.hexify(recno)+", sfi:"+HexString.hexify(sfi)+")",this);
        return response;
        
    }
    
    /** getChallenge returns an challenge which will be requested from the card
     *  This method isn't in use yet. 
     *  
     * 
     * @return challenge in an byte-array
     */
    public byte[] getChallenge() {
        
        byte[] gc_cmd = { (byte)0x00, (byte)0x84, (byte)0x00, (byte)0x00, (byte)0x08};
        
        command.setLength(0);
        command.append(gc_cmd);
        
        try {
            response = sendCommandAPDU(command);
        } catch (CardTerminalException cte) {
            System.out.println("CardTerminalException: ");
            System.out.println(cte.getMessage() );
        }
        return response.data();
    }
    
    /** This method reads out the properties from the ResponseAPDU from a selectFID() command 
     * 
     * @param resp The ResponseAPDU from a selectFID() command
     * @return a instance of FileProperties
     */
    private FileProperties getProperties(ResponseAPDU resp) {
        
        lt.info("called method getProperties(ResponseAPDU resp)", this);
        
        int freemem = 0;
        int df_descriptor = 0;
        int tof = 0;
        int recordlength = 0; //length of one record
        int records = 0; //number of record
        byte[] file_id = new byte[2];
        byte[] acs = null;
        byte[] fid_ef_rule = new byte[2];
        byte sfi = 0;
        
        if (resp.getByte(0)==(byte)0x62) {   // search FCP tag 62
            
            int j = 2;  // j is a pointer
            
            // Gibts nicht mehr! aber aus Angst bleibt er mal drin ;-)
            if ((byte)resp.getByte(j)==(byte)0x81) { // search freemem tag 81 (old!)
                j+=resp.getByte(j+1)+2;
            }
            
            if ((byte)resp.getByte(j)==(byte)0x82) {  // search file descriptor tag 82
                if (resp.getByte(j+1)==1) {  // get length of following data
                    tof = 0x38;
                    df_descriptor = resp.getByte(j+2);
                } else {
                    tof = resp.getByte(j+2)&(byte)0x0F;
                    recordlength = resp.getByte(j+5);
                    records = resp.getByte(j+6);
                }
                j+=resp.getByte(j+1)+2;
            }
            
            if ((byte)resp.getByte(j)==(byte)0x83) { // search file_id tag 83
                file_id[0] = (byte)resp.getByte(j+2);
                file_id[1] = (byte)resp.getByte(j+3);
                j+=resp.getByte(j+1)+2;;
            }
            
            if ((byte)resp.getByte(j)==(byte)0x84) { // search df name tag 84
                j+=resp.getByte(j+1)+2; // im not interested in the the df name so i skip it
            }
            
            if ((byte)resp.getByte(j)==(byte)0x84) { // Sometimes a fcp contains two AID-Tags(Tag 84)
                j+=resp.getByte(j+1)+2;
            }
            
            if ((byte)resp.getByte(j)==(byte)0x85) { // search for tag 85 (freemem if DF, filesize if ef)
                freemem = resp.getByte(j+2)*256+resp.getByte(j+3);
                j+=resp.getByte(j+1)+2;
            }
            
            if ((byte)resp.getByte(j)==(byte)0x88) { // search SFI tag 86
                sfi = (byte)resp.getByte(j+2);
                j+=resp.getByte(j+1)+2;
            }
            
            if ((byte)resp.getByte(j)==(byte)0xA1) { // search tag A1 (ACs links)
                int length = (byte)resp.getByte(j+1);
                acs = new byte[length];
                for (int k=0;k<length;k++) {
                    acs[k] = (byte)resp.getByte(j+2+k); //read the AC-Link
                }
            }
            
        } // end - if Tag 62
        
        byte readac = 0x0F;     //0x00: ALWAYAS;  0x0F: NEVER;  0x01:CHV1;  0x02:CHV2;
        byte updateac = 0x0F;   //0x00: ALWAYAS;  0x0F: NEVER;  0x01:CHV1;  0x02:CHV2;
        
        if (tof!=0x38) {
            if (readAllowed(acs)) readac = 0x00;
            else readac = 0x11; // random value to signal undef ac
        }
        
        FileProperties fp = new FileProperties(freemem, tof, recordlength, records, file_id, readac, updateac);
        return fp;
    }
    
    /** Read the EF_RULE and search for the AC for reading the file.
     *  readAllowed only searchs  for the ALW AC. IF there is an other
     *  AC then AWL it will interpret it as not readable, because JSmex don't
     *  support authentication or secure messaging for 'Geldkarten' for now.
     *
     * @param acl ByteArray with the link to the EF_RULE and the record which specifiy the ACL
     * @return returns true if found AC = ALW for reading the file
     */
    private boolean readAllowed(byte[] acl) {
        
        byte[] fid = new byte[2];
        fid[0] = acl[2];
        fid[1] = acl[3];
        
        selectFID((byte)0x02, (byte)0x0C, fid);
        ResponseAPDU resp = readRecord(acl[5], 0);
        
        for (int i=0;i<resp.data().length;i++) {
            if ((byte)resp.getByte(i)==(byte)0x80) {
                for (int j=i;j<resp.data().length;j++) {
                    if ((byte)resp.getByte(j+2)==(byte)0x81&&(byte)resp.getByte(j+3)==(byte)0x90&&(byte)resp.getByte(j+4)==(byte)0x00){
                        lt.info("called method readAllowed(byte[] acl). returns \"true\"",this);
                        return true;
                    }
                }
                
            }
        }
        
        lt.info("called method readAllowed(byte[] acl). returns \"false\"",this);
        
        return false;
        
    }
    
    /** At the moment there is no need to read transparent EFs.
     *  But because the class has to implement all abstract methods
     *  from JSmexCardService this class has this method. 
     *  So this method just return null.
     * 
     * @param oh 
     * @param ol 
     * @param length 
     * @return 
     */
    public ResponseAPDU readBinary(byte oh, byte ol, byte length) {
        return null;
    }
    
    /** At the moment JSmex doesn't support verifying CHVs on 'Geldkarten'.
     *  But because the class has to implement all abstract methods
     *  from JSmexCardService this class has this method. 
     *  So this method just return null.
     * 
     * @param chvindication 
     * @param chv 
     * @return null
     */
    public ResponseAPDU verifyCHV(byte chvindication, String chv) {
        return null;
    }
    
    /** At the moment JSmex doesn't support verifying CHVs on 'Geldkarten'.
     *  But because the class has to implement all abstract methods
     *  from JSmexCardService this class has this method. 
     *  So this method just return false.
     * 
     * @param chvindicator 
     * @return false
     */
    public boolean chvIsVerified(int chvindicator) {
        return false;
    }
    
    /** This method checks if there is an EF or DF with the fid, which comes in fidString,
     *  the currently selected DF. If a file was found, the method returns a new SmartCardFile 
     *  instance for the founded file. Else it return null.
     * 
     * @param fidString a String which contains the FID
     * @return a instance of SmartCardFile or null
     */
    public SmartCardFile getSmartCardFile(String fidString) {
        
        lt.info("called method getSmartCardFile(fidString: "+fidString+")",this);
        
        byte[] fid = JSmexTools.parseHexString(fidString);
        
        ResponseAPDU resp = selectDF(fid);
        String name;
        
        if (resp.sw()!=0x9000) {
            resp = selectEF(fid);
            if (resp.sw()!=0x9000) return null;
            
        }
        
        FileProperties fp = getProperties(resp);
        
        if (fp.isDF()) name = new String("DF ("+fidString+")");
        else name = new String("EF ("+fidString+")");
        
        SmartCardFile scfile = new SmartCardFile(name ,fp);
        return scfile;
    }
    
    /** At the moment JSmex doesn't support updating files on 'Geldkarten'.
     *  But because the class has to implement all abstract methods
     *  from JSmexCardService this class has this method. 
     *  So this method just return null.
     * 
     * @param oh 
     * @param ol 
     * @param data 
     * @return null
     */
    public ResponseAPDU updateBinary(byte oh, byte ol, byte[] data) {
        return null;
    }
    
    /** At the moment JSmex doesn't support updating files on 'Geldkarten'.
     *  But because the class has to implement all abstract methods
     *  from JSmexCardService this class has this method. 
     *  So this method just return null.
     * 
     * @param recno 
     * @param data 
     * @return 
     */
    public ResponseAPDU updateRecord(byte recno, byte[] data) {
        return null;
    }
    
    
}
